package com.virtualightning.core.processor.adapter.processor

import com.squareup.kotlinpoet.*
import com.virtualightning.anno.processor.BaseProcessor
import com.virtualightning.core.processor.adapter.anno.BindVHEvent
import com.virtualightning.core.processor.adapter.anno.Carry
import com.virtualightning.core.processor.adapter.anno.ViewHolderEventBinder
import java.util.*
import java.util.concurrent.Future
import java.util.concurrent.FutureTask
import javax.annotation.processing.SupportedOptions
import javax.annotation.processing.SupportedSourceVersion
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeMirror
import kotlin.collections.HashMap

/**
 *  Anchor : Create by CimZzz
 *  Time : 2021/11/22 11:30
 *
 */
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions(BaseProcessor.KAPT_KOTLIN_GENERATED_OPTION_NAME)
class AdapterEventBinder : BaseProcessor() {
	companion object {

		/**
		 * 类名: AdapterCommand
		 */
		const val CLASSNAME_AdapterCommand = "com.virtualightning.mvvm.impl.adapter.AdapterCommand"

		/**
		 * 生成文件后缀
		 */
		const val POSTFIX_Generated = "___AdapterEventBinder"
	}

	/**
	 * 支持的注解类型
	 * [BindVHEvent] 绑定 ViewHolder 事件
	 * @return MutableSet<String>
	 */
	override fun getSupportedAnnotationTypes(): MutableSet<String> {
		return mutableSetOf(
			BindVHEvent::class.java.canonicalName
		)
	}

	/**
	 * 处理注解
	 * @param elements MutableSet<out TypeElement>
	 * @return Boolean
	 */
	override fun onProcess(elements: MutableSet<out TypeElement>): Boolean {
		// 预定义 Map
		val map = HashMap<String, MutableList<BuildConfig>>()
		val firstStepFutureList = LinkedList<Future<BuildConfig>>()
		for (element in roundEnv.getElementsAnnotatedWith(BindVHEvent::class.java)) {
			val future = handleElementStep(element)
			exec(future)
			firstStepFutureList.add(future)
		}
		for (future in firstStepFutureList) {
			val buildConfig = future.get()
			map.computeIfAbsent(buildConfig.className.toString()) {
				LinkedList()
			}.add(buildConfig)
		}

		val secondStepFutureList = LinkedList<Future<Unit>>()
		// 生成对应文件
		map.forEach { (_, configList) ->
			val future = handleBuildConfigStep(configList)
			exec(future)
			secondStepFutureList.add(future)
		}

		secondStepFutureList.forEach {
			it.get()
		}

		return true
	}


	/**
	 * 异步处理注解，生成构建信息
	 * @param element Element 元素
	 */
	private fun handleElementStep(element: Element): FutureTask<BuildConfig> {
		return FutureTask {
			// 只能绑定方法
			if (!checkMethod(element)) {
				logError("BindVHEvent 注解只能用于方法")
			}
			val executableElement = element as ExecutableElement
			// 获取类名
			val classElement = element.enclosingElement
			if (!checkClass(classElement)) {
				logError("BindVHEvent 注解方法必须放到类中")
			}
			val className = (classElement as TypeElement).asClassName()
			val annotation = element.getAnnotation(BindVHEvent::class.java)
			val vhCls = getAnnotationClass(element, BindVHEvent::class.java, "vhCls")
			if(vhCls == null) {
				logError("没有找到 BindVHEvent 中指定的 ViewHolder 类")
			}
			val scene = annotation.scene
			val paramsList = LinkedList<ParamBundle>()
			var data: String? = null
			var passData: String? = null


			for (parameter in (executableElement.parameters ?: emptyList())) {
				val kotlinParam = javaTypeToKotlinType(parameter.asType().asTypeName())
				when (val paramStr = kotlinParam.toString()) {
					CLASSNAME_AdapterCommand -> {
						paramsList.add(
							ParamBundle(
								ParamBundle.TYPE_Command
							)
						)
					}
					else -> {
						val carryAnnotation = parameter.getAnnotation(Carry::class.java)
						if(carryAnnotation == null) {
							if(data == null || data == paramStr) {
								data = paramStr
								paramsList.add(
									ParamBundle(
										ParamBundle.TYPE_Data,
										data
									)
								)
							}
							else {
								logError("Data 类型不同，无法区分: \"$paramStr\" and \"$data\"")
							}
						}
						else {
							if (passData == null || passData == paramStr) {
								// 可以
								passData = paramStr
								paramsList.add(
									ParamBundle(
										ParamBundle.TYPE_Pass,
										passData
									)
								)
							} else {
								logError("Pass Data 类型不同，无法区分: \"$paramStr\" and \"$passData\"")
							}

						}
					}
				}
			}

			BuildConfig(
				className = className,
				methodName = element.simpleName.toString(),
				vhCls = vhCls!!,
				scene = scene,
				paramsList = paramsList
			)
		}
	}

	/**
	 * 异步处理文件生成
	 * @param buildConfig List<BuildConfig> 构建信息
	 */
	private fun handleBuildConfigStep(buildConfig: List<BuildConfig>): FutureTask<Unit> {
		return FutureTask {
			val className = buildConfig.first().className
			val commandCls = Class.forName(CLASSNAME_AdapterCommand)
			val commandClsName = commandCls.asClassName()
			val fileSpecBuilder = FileSpec.builder(
				className.packageName(),
				className.simpleName() + POSTFIX_Generated
			)

			// 生成方法
			val funcSpecBuilder = FunSpec.builder("handleAdapterCommand")
				.addComment("Process and analyze adapter command for outer, match which annotated method")
				.addParameter("instance", className)
				.addParameter("command", commandClsName)

			// 遍历 BuildConfig，根据 Scene 分组
			val sceneMap = buildConfig.groupBy {
				it.scene
			}

			funcSpecBuilder.addStatement("when(command.scene) {")
			sceneMap.forEach { (scene, configList) ->
				funcSpecBuilder.addStatement("\t$scene -> {")
				funcSpecBuilder.addStatement("\t\twhen(command.viewHolderCls) {")
				configList.forEach { config ->
					val vhTypeName = config.vhCls.asTypeName()
					val splitClassName = vhTypeName.toString().split(".")
					val vhPackageName = splitClassName.subList(0, splitClassName.size - 1).joinToString(separator = ".")
					val vhSimpleName = splitClassName.last()
					val vhClassName = ClassName(vhPackageName, vhSimpleName)
					val paramsList = config.paramsList
					val methodName = config.methodName

					fileSpecBuilder.addAliasedImport(vhClassName, vhSimpleName)
					funcSpecBuilder.addStatement("\t\t\t${vhSimpleName}::class.java -> {")
					if (paramsList.isEmpty()) {
						funcSpecBuilder.addStatement("\t\t\t\tinstance.$methodName()")
					} else {
						var isFirst = true
						var hasData = false
						var hasPassData = false
						val extendStr = buildString {
							append("\t\t\t\tinstance.$methodName(")
							paramsList.forEach { param ->
								if (!isFirst) {
									append(", ")
								}
								when (param.type) {
									ParamBundle.TYPE_Command -> {
										append("command")
									}
									ParamBundle.TYPE_Pass -> {
										if (!hasPassData) {
											val paramStr = param.other as String
											val genericIndex = paramStr.indexOf('<')
											var generics: String? = null
											val paramClsName = if(genericIndex == -1) {
												paramStr
											} else {
												generics = paramStr.substring(genericIndex)
												paramStr.substring(0, genericIndex)
											}
											val splitPassClassName = paramClsName.split(".")
											val passPackageName = splitPassClassName.subList(0, splitPassClassName.size - 1).joinToString(separator = ".")
											val passSimpleName = splitPassClassName.last()
											val passClassName = ClassName(passPackageName, passSimpleName)
											val passDataName = passClassName.simpleName()
											fileSpecBuilder.addAliasedImport(
												passClassName,
												passDataName
											)
											funcSpecBuilder.addStatement("\t\t\t\tval carryData = command.carryData as? $passDataName${generics ?: ""} ?: return")
											hasPassData = true
										}
										append("carryData")
									}
									ParamBundle.TYPE_Data -> {
										if (!hasData) {
											val paramStr = param.other as String
											val genericIndex = paramStr.indexOf('<')
											var generics: String? = null
											val paramClsName = if(genericIndex == -1) {
												paramStr
											} else {
												generics = paramStr.substring(genericIndex)
												paramStr.substring(0, genericIndex)
											}
											val splitPassClassName = paramClsName.split(".")
											val passPackageName = splitPassClassName.subList(0, splitPassClassName.size - 1).joinToString(separator = ".")
											val passSimpleName = splitPassClassName.last()
											val passClassName = ClassName(passPackageName, passSimpleName)
											val passDataName = passClassName.simpleName() + (generics ?: "")
											fileSpecBuilder.addAliasedImport(
												passClassName,
												passDataName
											)
											funcSpecBuilder.addStatement("\t\t\t\tval data = command.data as? $passDataName ?: return")
											hasData = true
										}
										append("data")
									}
								}
								isFirst = false
							}
							append(")")
						}
						funcSpecBuilder.addStatement(extendStr)
					}
					funcSpecBuilder.addStatement("\t\t\t}")
				}
				funcSpecBuilder.addStatement("\t\t}")
				funcSpecBuilder.addStatement("\t}")
			}
			funcSpecBuilder.addStatement("}")
			funcSpecBuilder.addModifiers(KModifier.OVERRIDE)
			val typeSpec = TypeSpec.classBuilder(className.simpleName() + POSTFIX_Generated)
				.addModifiers(KModifier.PUBLIC)
				.superclass(
					ParameterizedTypeName.get(
						ViewHolderEventBinder::class.java.asClassName(),
						className,
						commandClsName
					),
				)
				.addFunction(funcSpecBuilder.build())
			fileSpecBuilder.addType(typeSpec.build())
			fileSpecBuilder.addComment("Whole the code of this file is \"Auto-Generated\", do not modify by manual")
			val fileSpec = fileSpecBuilder.build()
			fileSpec.writeTo(dir2)
		}
	}

	/**
	 * 参数类型配置
	 */
	private data class ParamBundle(
		/**
		 * 参数类型
		 */
		val type: Int,

		/**
		 * 其他参数
		 */
		val other: Any? = null
	) {
		companion object {
			/**
			 * AdapterCommand
			 */
			const val TYPE_Command = 0

			/**
			 * 数据类型
			 */
			const val TYPE_Data = 1

			/**
			 * PassData
			 */
			const val TYPE_Pass = 2
		}
	}

	/**
	 * 构建配置
	 */
	private data class BuildConfig (
		/**
		 * 类名
		 */
		val className: ClassName,

		/**
		 * 方法名
		 */
		val methodName: String,

		/**
		 * ViewHolder Class
		 */
		val vhCls: TypeMirror,

		/**
		 * Scene 场景
		 */
		val scene: Int,

		/**
		 * 参数类型
		 */
		val paramsList: List<ParamBundle>
	)
}